/*
 * Copyright (C) 2021 Baidu, Inc. All Rights Reserved.
 */
package com.chukun.rpc.provider;


import java.net.InetAddress;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executors;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanPostProcessor;

import com.chukun.rpc.core.TinyRpcServiceHelper;
import com.chukun.rpc.core.TinyRpcServiceMetaData;
import com.chukun.rpc.protocol.codec.TinyRpcDecoder;
import com.chukun.rpc.protocol.codec.TinyRpcEncoder;
import com.chukun.rpc.protocol.handler.TinyRpcRequestHandler;
import com.chukun.rpc.provider.annotation.TinyRpcService;
import com.chukun.rpc.registry.RegistryService;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import lombok.extern.slf4j.Slf4j;

/**
 * <p>rpc 服务提供者处理bean<p/>
 *
 * @author chukun
 * @version 1.0
 * @since 1.0-SNAPSHOT
 */
@Slf4j
public class TinyRpcProvider  implements InitializingBean, BeanPostProcessor {
    private String serverAddress;
    private final int serverPort;
    private final RegistryService serviceRegistry;

    private final Map<String, Object> rpcServiceMap = new HashMap<>();

    public TinyRpcProvider(int serverPort, RegistryService serviceRegistry) {
        this.serverPort = serverPort;
        this.serviceRegistry = serviceRegistry;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
       Executors.newSingleThreadExecutor().submit(() -> {
            try {
                startRpcServer();
            }catch (Exception e) {
                log.error("Start rpc server error", e);
            }
        });
    }

    private void startRpcServer() throws Exception {
        this.serverAddress = InetAddress.getLocalHost().getHostAddress();
        EventLoopGroup boss = new NioEventLoopGroup();
        EventLoopGroup worker = new NioEventLoopGroup();
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(boss, worker)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline()
                                    .addLast(new TinyRpcEncoder())
                                    .addLast(new TinyRpcDecoder())
                                    .addLast(new TinyRpcRequestHandler(rpcServiceMap));
                        }
                    })
                    .childOption(ChannelOption.SO_KEEPALIVE, true);

            ChannelFuture channelFuture = bootstrap.bind(this.serverAddress, this.serverPort).sync();
            log.info("Server addr {} started on port {}", this.serverAddress, this.serverPort);
            channelFuture.channel().closeFuture().sync();
        }finally {
            worker.shutdownGracefully();
            boss.shutdownGracefully();
        }
    }

    /**
     * bean的后置处理器对rpc相关的bean做注册处理
     * @param bean
     * @param beanName
     * @return
     * @throws BeansException
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

        TinyRpcService rpcService = bean.getClass().getAnnotation(TinyRpcService.class);
        if (rpcService != null) {
            String serviceName = rpcService.serviceInterface().getName();
            String version = rpcService.version();
            try {
                // 创建服务的元数据
                TinyRpcServiceMetaData metaData = new TinyRpcServiceMetaData();
                metaData.setServiceAddr(this.serverAddress);
                metaData.setPort(this.serverPort);
                metaData.setServiceName(serviceName);
                metaData.setVersion(version);
                // 注册服务
                serviceRegistry.register(metaData);
                // 将服务缓存到rpcServiceMap中
                rpcServiceMap.put(TinyRpcServiceHelper.buildServiceKey(serviceName, version), bean);
            }catch (Exception e) {
                log.error("Failed to register service {}#{}", serviceName, version, e);
            }
        }
        return bean;
    }
}
